package com.rose.mall.seckill.service.impl;


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.rose.common.to.mq.SecKillOrderTo;
import com.rose.common.utils.R;
import com.rose.common.vo.MemberRsepVo;
import com.rose.mall.seckill.feign.CouponFeignService;
import com.rose.mall.seckill.feign.ProductFeignService;
import com.rose.mall.seckill.interceptor.LoginUserInterceptor;
import com.rose.mall.seckill.service.SeckillService;
import com.rose.mall.seckill.to.SeckillSkuRedisTo;
import com.rose.mall.seckill.vo.SeckillSessionsWithSkus;
import com.rose.mall.seckill.vo.SeckillSkuRelationEntity;
import com.rose.mall.seckill.vo.SkuInfoVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;

import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;


/**
 * <p>Title: SeckillServiceImpl</p>
 * Description：
 * date：2020/7/6 17:33
 */
@Slf4j
@Service
public class SeckillServiceImpl implements SeckillService {

	@Autowired
	private RabbitTemplate rabbitTemplate;

	@Autowired
	CouponFeignService couponFeignService;
	@Autowired
	StringRedisTemplate stringRedisTemplate;

	@Autowired
	ProductFeignService productFeignService;

	@Autowired
	RedissonClient redissonClient;

	private final String SESSION_CACHE_PREFIX = "seckill:sessions:";

	private final String SKUKILL_CACHE_PREFIX = "seckill:skus:";

	private final String SKUSTOCK_SEMAPHONE = "seckill:stock:"; // +商品随机码

	@Override
	public void uploadSeckillSkuLatest3Day() {

		R r = couponFeignService.getLate3DaySession();
		if(r.getCode() == 0){
			//上架
			List<SeckillSessionsWithSkus> data = r.getData(new TypeReference<List<SeckillSessionsWithSkus>>() {
			});
			System.out.println("uploadSeckillSkuLatest3Day===>");

			//缓存活动信息
			saveSessionInfo(data);


			//缓存商品信息
			saveSessionSkuInfos(data);
		}

	}

	@Override
	public String kill(String killId, String key, Integer num) {
		MemberRsepVo memberRsepVo = LoginUserInterceptor.threadLocal.get();
		BoundHashOperations<String, String, String> hashops = stringRedisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
		String s = hashops.get(killId);
		if(StringUtils.isEmpty(s)){
			return null;
		}else{
			SeckillSkuRedisTo redis = JSON.parseObject(s, SeckillSkuRedisTo.class);
			//校验合法性
			Long startTime = redis.getStartTime();
			Long endTime = redis.getEndTime();
			long time = new Date().getTime();
			if(time >= startTime && time<= endTime){
				//2.校验随机码
				String randomCode = redis.getRandomCode();
				String   skuId = redis.getPromotionSessionId()+"-"+redis.getSkuId();
				if(randomCode.equals(key) && killId.equals(skuId)){
					//3.验证购物数量是否合理
					if(num<= redis.getSeckillLimit().intValue()){
						//4.验证这个人是否已经购买过了
						String redisKey = memberRsepVo.getId() + "-" + skuId;
						// 让数据自动过期
						long ttl = redis.getEndTime() - redis.getStartTime();
						Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent(redisKey, num.toString(), ttl<0?0:ttl, TimeUnit.MILLISECONDS);
						if (aBoolean) {
							// 占位成功 说明从来没买过
							RSemaphore semaphore = redissonClient.getSemaphore(SKUSTOCK_SEMAPHONE + randomCode);
							boolean acquire = false;
							try {
								acquire = semaphore.tryAcquire(num,100, TimeUnit.MILLISECONDS);
								if(acquire){
									//秒杀成功 ，快速下单 发送消息

									String orderSn = IdWorker.getTimeId() + UUID.randomUUID().toString().replace("-","").substring(7,8);
									SecKillOrderTo orderTo = new SecKillOrderTo();
									orderTo.setOrderSn(orderSn);
									orderTo.setMemberId(memberRsepVo.getId());
									orderTo.setNum(num);
									orderTo.setSkuId(redis.getSkuId());
									orderTo.setSeckillPrice(redis.getSeckillPrice());
									orderTo.setPromotionSessionId(redis.getPromotionSessionId());
									rabbitTemplate.convertAndSend("order-event-exchange","order.seckill.order", orderTo);
									return orderSn;
								}
							} catch (InterruptedException e) {
								return null;
							}

						}else {
							return  null;
						}
					}


				}else {
					return null;
				}
			}else{
				return  null;
			}
		}



		return null;
	}

	@Override
	public List<SeckillSkuRedisTo> getCurrentSeckillSkus() {
		//1.确定当前时间属于那个场次
		long time = new Date().getTime();
		//2.获取当前的秒杀场次所需的所有信息
		Set<String> keys = stringRedisTemplate.keys(SESSION_CACHE_PREFIX + "*");
		for (String key : keys) {
			String replace = key.replace(SESSION_CACHE_PREFIX, "");
			String[] split = replace.split("_");
			long start = Long.parseLong(split[0]);
			long end = Long.parseLong(split[1]);
			if(time >= start && time <= end){
				List<String> range = stringRedisTemplate.opsForList().range(key, 0, 100);
				BoundHashOperations<String, String, String> hashOps = stringRedisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
				//获取多个key的数据 multiGet
				List<String> list = hashOps.multiGet(range);
				if(list != null){
					return list.stream().map(item -> {
						SeckillSkuRedisTo redisTo = JSON.parseObject(item, SeckillSkuRedisTo.class);
						//当前秒杀开始了 是需要随机码的
//						redisTo.setRandomCode(null);
						return redisTo;
					}).collect(Collectors.toList());
				}
				break;

			}
		}
		return null;
	}

	@Override
	public SeckillSkuRedisTo getSkuSeckillInfo(Long skuId) {
		BoundHashOperations<String, String, String> hashOps = stringRedisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
		Set<String> keys = hashOps.keys();
		if(keys != null && keys.size() > 0){
			String regx = "\\d-" + skuId;
			for (String key : keys) {
				if (Pattern.matches(regx, key)) {
					String json = hashOps.get(key);
					SeckillSkuRedisTo to = JSON.parseObject(json, SeckillSkuRedisTo.class);
					// 处理一下随机码
					long current = new Date().getTime();
					if(current <= to.getStartTime() || current >= to.getEndTime()){
						to.setRandomCode(null);
					}
					return to;
				}

			}
		}

		return null;
	}

	private void saveSessionInfo(List<SeckillSessionsWithSkus> sessions){
		if(sessions != null){
			sessions.stream().forEach(session -> {
				long startTime = session.getStartTime().getTime();

				long endTime = session.getEndTime().getTime();
				String key = SESSION_CACHE_PREFIX + startTime + "_" + endTime;
				Boolean hasKey = stringRedisTemplate.hasKey(key);
				if(!hasKey){
					// 获取所有商品id
					List<String> collect = session.getRelationSkus().stream().map(item -> item.getPromotionSessionId() + "-" + item.getSkuId()).collect(Collectors.toList());
					// 缓存活动信息
					stringRedisTemplate.opsForList().leftPushAll(key, collect);
				}
			});
		}
	}
	private void saveSessionSkuInfos(List<SeckillSessionsWithSkus> sessions){
		sessions.stream().forEach(session -> {
			//准备hash
			BoundHashOperations<String, Object, Object> ops = stringRedisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);

			session.getRelationSkus().stream().forEach(seckillSkuVo->{
				String token = UUID.randomUUID().toString().replace("-","");
				if(!ops.hasKey(seckillSkuVo.getPromotionSessionId() + "-" + seckillSkuVo.getSkuId())){
					SeckillSkuRedisTo redisTo = new SeckillSkuRedisTo();
					//1.sku的基本数据
					R skuInfo = productFeignService.getSkuInfo(seckillSkuVo.getSkuId());
					if (skuInfo.getCode()==  0 ){
						SkuInfoVo info = skuInfo.getData("skuInfo", new TypeReference<SkuInfoVo>() {
						});
						redisTo.setSkuInfoVo(info);

					}
					//2.sku的秒杀信息

					//3.设置上当前商品的秒杀时间信息
					redisTo.setStartTime(session.getStartTime().getTime());
					redisTo.setEndTime(session.getEndTime().getTime());
					//4.随机码

					redisTo.setRandomCode(token);
					//5.使用库存作为分布式的信号量

					RSemaphore semaphore = redissonClient.getSemaphore(SKUSTOCK_SEMAPHONE + token);
					//将商品可以秒杀的数量作为信号量
					semaphore.trySetPermits(seckillSkuVo.getSeckillCount().intValue());


					BeanUtils.copyProperties(seckillSkuVo,redisTo);
					//缓存商品
					String s = JSON.toJSONString(redisTo);
					ops.put(seckillSkuVo.getPromotionSessionId() + "-" + seckillSkuVo.getSkuId(), JSON.toJSONString(redisTo));
				}
//				ops.put(seckillSkuVo.getId().toString(), s);
			});
		});

	}
}